const path = require('path');
const compressing = require('compressing');
const fs = require('fs-extra');
const moment = require('moment');
const ProgressIndicator = require('../../../progress-indicator');
const { versionType, getOverview, getVersion } = require('../../../project-versions');
const BaseReport = require('../base-report');
const classCreator = require('./../base-report-test-case-transformer');
const { resultKind, testKind, coverageKind } = require('../../../tests');
const testModels = require('./../../../tests');
const functionModels = require('./../../../functions');
const utility = require('../../../utility');
const logger = require('../../../logger');
const packageJson = require('../../../../../package.json');
const { getCountableKinds } = require('../../../file-elements');

const BATCH_SIZE = 200; // 按用例数量进行拆分，同一个函数的用例不拆分到多个文档
class Report17 extends BaseReport {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format = null, complete = false) {
    super(versionId, locale, version, project, creator, outputDir, null, format);
    this.baseDir = __dirname;
    this.complete = complete;
    this.ReportTestCaseTransformer = classCreator('unitTest');
    this.functionList = [];
    this.fileCoverageList = [];
    this.testCaseList = [];
    this.coverageKind = [];

    this.functionResults = [];

    this.reportTime = new Date();

    this.failedCaseDetails = []; // 不通过用例列表(问题报告单)

    this.canGenerateReport = true; // 是否生成报告，如果为false时，仅构建数据但不生成报告
  }

  /**
   * 报告生成
   */
  async generate(progressIndicator = new ProgressIndicator()) {
    const [progress1, progress2] = progressIndicator.split([0.7, 0.3]);
    const result = {
      cachePath: this.outputDir,
    };
    // 只展示这3种
    const nameMap = {
      statement: '语句',
      branch: '分支',
      mcdc: 'MC/DC',
    };
    Object.keys(nameMap).forEach((key) => {
      if (this.version.coverages & coverageKind[key]) {
        this.coverageKind.push({
          key,
          name: nameMap[key],
        });
      }
    });
    await this.spliteByFile(null, progress1);
    await this.generateSummaryReport();
    progress2.advance(0.5);
    if (this.canGenerateReport) {
      const fileName = `${this.project.projectName}-${this.version.versionName}.${this.getReportName()}.${this.format}.zip`;
      result.reportPath = path.join(this.outputDir, fileName);
      const reportsDir = path.join(this.outputDir, this.project.projectName);
      await compressing.zip.compressDir(reportsDir, result.reportPath);
      progress2.finish();
      return result;
    }
    return null;
  }

  isRegressionTest() {
    return this.version.oldVersionId !== undefined;
  }

  async generateSummaryReport() {
    const template = 'summary';
    const output = path.join(
      this.outputDir,
      this.project.projectName,
      this.version.versionName,
      `${this.project.projectName + (this.isIntegrationTest() ? '集成' : '单元')}测试报告`
    );
    const fileMap = this.functionList.reduce((fileMap, func) => {
      const { filePath } = func;
      fileMap.set(filePath, fileMap.get(filePath) || []);
      fileMap.get(filePath).push(func);
      return fileMap;
    }, new Map());
    const fileList = [];
    // eslint-disable-next-line no-restricted-syntax, prefer-const
    for (let [filePath, functionList] of fileMap) {
      functionList = functionList.filter((func) => func.testCounts.total > 0);
      if (functionList.length > 0) {
        fileList.push({
          filePath,
          functionList,
        });
      }
    }

    const summaryTested = {
      isRegressionTest: this.isRegressionTest(),

      fileCount: 0,
      functionCount: 0,
      numberOfLines: 0,
      sourceLines: 0,
      commentLines: 0,

      projectName: this.project.projectName,
      versionName: this.version.versionName,
      reportTime: moment(this.reportTime).format('YYYY年M月D日'),
      createdTime: moment(this.project._id.getTimestamp()).format('YYYY年M月D日'),
      reportTimeNumber: moment(this.reportTime).format('YYYYMMDD'),
      createdTimeNumber: moment(this.project._id.getTimestamp()).format('YYYYMMDD'),
      testType: this.isIntegrationTest() ? '集成' : '单元',
      toolVersion: packageJson.version,

      testCounts: {
        passed: 0,
        notPassed: 0,
        notExcuted: 0,
        added: 0,
        total: 0,
      },

      language: this.version.language,

    };
    const filesSet = new Set();
    this.functionList
      .filter((item) => item.testCounts.total > 0) // 只统计有用例的函数
      .forEach((func) => {
        summaryTested.functionCount += 1;
        summaryTested.numberOfLines += func.numberOfLines;
        summaryTested.sourceLines += func.sourceLines;
        summaryTested.commentLines += func.commentLines;
        filesSet.add(func.filePath);

        Object.keys(summaryTested.testCounts).forEach((key) => {
          const cnt = func.testCounts[key] || 0;
          summaryTested.testCounts[key] += cnt;
        });
      });
    summaryTested.fileCount = filesSet.size;

    // 有覆盖率低于百分之百的
    const fileCoverageListNotFull = this.fileCoverageList.reduce((arr, item) => {
      const { functions } = item;
      const functionsNotFull = functions.filter((f) => f.coverages.find((cov) => !cov.allCovered));
      if (functionsNotFull.length) {
        arr.push({
          ...item,
          functions: functionsNotFull,
        });
      }
      return arr;
    }, []);

    let issueNumber = 0;
    // note:!!! 确保this.failedCaseDetails是按函数排序的
    const functionIssueMap = this.failedCaseDetails.reduce((obj, tc) => {
      issueNumber += 1;
      tc.issueNumber = issueNumber;
      tc.issueIdentifier = `DT_${`${issueNumber}`.padStart(2, '0')}`;
      const functionId = tc.functionId.toString();
      obj[functionId] = obj[functionId] || [];
      obj[functionId].push(tc.issueIdentifier);
      return obj;
    }, {});

    this.functionList.forEach((func) => {
      const functionId = func.functionId.toString();
      func.issues = functionIssueMap[functionId] || [];
      let [firstIssue = ''] = func.issues;
      if (func.issues.length > 1) {
        const lastIssue = func.issues[func.issues.length - 1];
        firstIssue += `-${lastIssue}`;
      }
      func.issuesStr = firstIssue;
    });

    const fileFunctionResultDic = this.functionResults.reduce((obj, item) => {
      const { filePath } = item;
      obj[filePath] = obj[filePath] || [];
      obj[filePath].push(item);
      return obj;
    }, {});
    const fileFunctionResults = Object.keys(fileFunctionResultDic).reduce((arr, key) => {
      arr.push({
        filePath: key,
        functions: fileFunctionResultDic[key],
      });
      return arr;
    }, []);

    const overview = await getOverview(this.version._id);
    overview.fileCount = overview.sourceFileCount + overview.headerFileCount;
    overview.sourceLines = overview.numberOfLines - overview.emptyLines - overview.commentLines;
    overview.commentRate = this.toPercentage(overview.commentLines / overview.numberOfLines, 2);
    overview.commentRateDesc = overview.commentRate >= 20 ? '符合' : '不符合';

    const {
      cycleComplexitySum, // 函数圈复杂度之和
      highCycleComplexityCnt, // 圈复杂度大于20的个数
      cycleComplexityMax, // 最大圈复杂度
      numberOfLinesSum, // 函数行数之和
    } = this.functionList.reduce((obj, func) => {
      const { cycleComplexity, numberOfLines } = func;
      obj.cycleComplexitySum += cycleComplexity;
      obj.numberOfLinesSum += numberOfLines;
      if (cycleComplexity > obj.cycleComplexityMax) {
        obj.cycleComplexityMax = cycleComplexity;
      }
      if (cycleComplexity > 20) {
        obj.highCycleComplexityCnt += 1;
      }
      return obj;
    }, {
      cycleComplexitySum: 0, // 函数圈复杂度之和
      highCycleComplexityCnt: 0, // 圈复杂度大于20的个数
      cycleComplexityMax: 0, // 最大圈复杂度
      numberOfLinesSum: 0, // 函数行数之和
    });
    const funcCount = this.functionList.length;
    overview.cycleComplexityAverage = (cycleComplexitySum / funcCount).toFixed(2);
    overview.cycleComplexityAverageDesc = overview.cycleComplexityAverage <= 10 ? '符合' : '不符合';
    overview.cycleComplexityMax = cycleComplexityMax;
    overview.cycleComplexityMaxDesc = overview.cycleComplexityMax <= 80 ? '符合' : '不符合';
    overview.numberOfLinesAverage = (numberOfLinesSum / funcCount).toFixed(2);
    overview.numberOfLinesAverageDesc = overview.numberOfLinesAverage <= 200 ? '符合' : '不符合';
    overview.cycleComplexityHighRate = this.toPercentage(highCycleComplexityCnt / funcCount, 2);
    overview.cycleComplexityHighRateDesc = overview.cycleComplexityHighRate <= 20 ? '符合' : '不符合';

    // 当前是回归版本，生成原始版本数据
    let reportOld;
    if (this.isRegressionTest()) {
      const { oldVersionId } = this.version;
      const oldVersion = await getVersion(oldVersionId);
      const report = new Report17(oldVersionId, this.i18n, oldVersion, this.project, this.creator, this.outputDir, this.format, this.complete);
      report.canGenerateReport = false;
      await report.generate();
      // console.log(report);
      reportOld = report;
    }

    this.functionList = this.functionList.filter((func) => func.testCounts.total > 0); // 过滤掉没用例的

    if (this.canGenerateReport) {
      return this.generateReport(template, {
        functionList: this.functionList,
        functionList1: this.isRegressionTest() ? reportOld.functionList : this.functionList,
        functionList2: this.isRegressionTest() ? this.functionList : undefined,
        fileList,
        fileCoverageList: this.fileCoverageList,
        fileCoverageList1: this.isRegressionTest() ? reportOld.fileCoverageList : this.fileCoverageList,
        fileCoverageList2: this.isRegressionTest() ? this.fileCoverageList : undefined,
        fileCoverageListNotFull, // 有覆盖率未达到100%
        testCaseList: this.testCaseList,
        functionResults: this.functionResults,
        fileFunctionResults,
        coverageKind: this.coverageKind,
        testType: this.isIntegrationTest() ? '集成' : '单元',
        failedCaseDetails: this.failedCaseDetails,

        overview,
        summary: summaryTested,
      }, output);
    }
    return null;
  }

  async generateTestCaseReport(functionDetails, fileInfo) {
    const template = 'testcase';
    let functionDetailsList = [];
    let counter = 0;
    let globalIndex = 0;
    let batchCounter = 1;
    for (let i = 0; i < functionDetails.length; i += 1) {
      const f = functionDetails[i];
      // eslint-disable-next-line no-loop-func
      f.tests.forEach((t) => {
        t.globalIndex = globalIndex + 1;
        globalIndex += 1;
        counter += 1;
      });
      functionDetailsList.push(f);
      if (counter >= BATCH_SIZE || i === functionDetails.length - 1) { // 拆分
        const output = path.join(
          this.outputDir,
          this.project.projectName,
          this.version.versionName,
          `${this.project.projectName + (this.isIntegrationTest() ? '集成' : '单元')}测试用例执行单`,
          `${fileInfo.path + (this.isIntegrationTest() ? '集成' : '单元')}测试用例执行单${batchCounter === 1 ? '' : `_${batchCounter}`}`
        );
        // eslint-disable-next-line no-await-in-loop
        await fs.ensureDir(path.dirname(output));
        if (this.canGenerateReport) {
          // eslint-disable-next-line no-await-in-loop
          await this.generateReport(template, {
            functionDetails: functionDetailsList,
            projectName: this.project.projectName,
            versionName: this.version.versionName,
            reportTime: moment(this.reportTime).format('YYYYMMDD'),
            createdTime: moment(this.project._id.getTimestamp()).format('YYYYMMDD'),
            testType: this.isIntegrationTest() ? '集成' : '单元',
            toolVersion: packageJson.version,
          }, output, { generateToc: true });
          counter = 0;
          functionDetailsList = [];
          batchCounter += 1;
        }
      }
    }
  }

  async handleFile(file, versionDir) {
    const result = await super.handleFile(file, versionDir);
    // console.log(result);
    const { functions: functionAll, file: f, typeSystem } = result;
    const fileCoverage = {
      fileName: f.path.replace(/^\.\//, ''),
      functions: [],
    };
    const functions = functionAll.filter((func) => getCountableKinds().find((kind) => kind === func.kind));
    functions.forEach((func, funcIdx) => {
      const { tests } = func;
      const functionResult = {
        tests: [],
        notPassed: [],
      };

      const counts = {
        passed: 0,
        notPassed: 0,
        notExcuted: 0,
        added: 0,
        total: 0,
      };

      let idIncreasement = 0;
      tests.forEach((t) => {
        // 这几个不统计
        if (t.resultKind === resultKind.runtimeError
          || t.resultKind === resultKind.timeout
          || t.resultKind === resultKind.systemError
          || t.resultKind === resultKind.instrumentError
          || t.resultKind === resultKind.compileError) {
          return;
        }
        counts.total += 1;
        let passed = false;
        if (t.kind === testKind.modified) {
          counts.added += 1;
        }
        switch (t.resultKind) {
          case resultKind.unknown:
            counts.notExcuted += 1;
            break;
          case resultKind.succeed:
          case resultKind.noResult:
          case resultKind.nonStrictSucceed:
            counts.passed += 1;
            passed = true;
            break;
          case resultKind.failed:
            counts.notPassed += 1;
            break;
          default:
            break;
        }
        if (t.resultKind !== resultKind.unknown) {
          idIncreasement += 1; // 未执行用例不展示，也不参与index计算
          const identifier = `${func.functionName}_${`${idIncreasement}`.padStart(2, '0')}`;
          t.identifier = identifier; // 覆盖原始identifier
          this.testCaseList.push({
            identifier,
            resultKind: t.resultKind,
            functionName: func.functionName,
            index: idIncreasement,
            passed,
          });
          functionResult.tests.push(identifier);
          if (!passed) {
            functionResult.notPassed.push(identifier);
          }
        }
      });
      const item = {
        index: funcIdx + 1,
        functionName: func.functionName,
        fileName: f.fileName,
        filePath: f.path,
        testCounts: counts,
        numberOfLines: func.numberOfLines,
        sourceLines: func.sourceLines,
        commentLines: func.commentLines,
        cycleComplexity: func.cycleComplexity,
        functionId: func._id,
      };
      this.functionList.push(item);
      if (functionResult.tests.length) {
        this.functionResults.push({
          identifier: functionResult.tests.length === 1 ? [functionResult.tests[0]] : `${functionResult.tests[0]}-${functionResult.tests[functionResult.tests.length - 1]}`,
          result: functionResult.notPassed.length > 0 ? '不通过' : '通过',
          functionName: func.functionName,
          fileName: f.fileName,
          filePath: f.path,
          fileId: f._id,
        });
      }

      const coverages = [];
      this.coverageKind.forEach(({ key, name }) => {
        const covered = func[`${key}Covered`] || 0;
        const total = func[`${key}Total`] || 0;
        const value = total ? Math.round((covered / total) * 100, 10) : '-';
        coverages.push({
          name,
          value,
          allCovered: total === covered, // 是否达到百分之百覆盖
          valueStr: value === '-' ? value : `${value}%`,
        });
      });
      if (counts.total > 0) {
        // 过滤掉没用例的
        fileCoverage.functions.push({
          functionName: func.functionName,
          coverages,
          testCounts: counts,
        });
      }
    });
    // 按覆盖率排序
    fileCoverage.functions = fileCoverage.functions.sort((f1, f2) => {
      for (let i = 0; i < this.coverageKind.length; i += 1) {
        let [cov1, cov2] = [f1.coverages[i].value, f2.coverages[i].value];
        cov1 = cov1 === '-' ? -1 : cov1;
        cov2 = cov2 === '-' ? -1 : cov2;
        if (cov1 !== cov2) {
          return cov1 > cov2 ? -1 : 1;
        }
      }
      return 0;
    });
    if (fileCoverage.functions.length) {
      this.fileCoverageList.push(fileCoverage);
    }
    const functionDetails = await this.getFunctionDetailsOfFile(functions, typeSystem, file);
    await this.generateTestCaseReport(functionDetails, f);
  }

  filterTestCasesByResultKind() {
    return false;
  }

  static versionTypeSupport(vType) {
    return vType === versionType.unitTest || vType === versionType.integrationTest;
  }

  /**
     *  获取函数详情
     * @param {Array[Object]} functions 函数
     * @param {Object} typeSystem 文件类型系统
     */
  async getFunctionDetailsOfFile(functions, typeSystem, file) {
    const functionsDetails = [];
    await utility.arrayChunkOperation(functions, 10, async (func) => {
      if (func.tests.length === 0) {
        return;
      }
      const funcVariables = functionModels.getFunctionVariableJson(func.variables);
      const testcasesDetails = await this.getTestDetailsOfFunction(
        typeSystem,
        func,
        funcVariables,
        func.tests,
        file,
      );
      if (testcasesDetails && testcasesDetails.length) {
        functionsDetails.push({
          functionName: func.functionName,
          index: func.index,
          tests: testcasesDetails,
        });
      }
    });
    return functionsDetails;
  }

  toPercentage(decimal, decimalPlaces) {
    if (typeof decimal !== 'number' || typeof decimalPlaces !== 'number') {
      throw new Error('Both arguments must be numbers');
    }
    return Math.round(decimal * 100 * (10 ** decimalPlaces)) / 100;
  }

  /**
   * 获取测试用例详情
   * @param {Object} typeSystem 文件类型系统
   * @param {Object} func  函数
   * @param {Object} funcVariables 函数variable
   * @param {Array[Object]} testcases 测试用例
   */
  async getTestDetailsOfFunction(typeSystem, func, funcVariables, testcases, file) {
    const testcasesDetails = [];
    testcases
      // eslint-disable-next-line max-len
      .filter((tc) => [resultKind.succeed, resultKind.noResult, resultKind.nonStrictSucceed, resultKind.failed].find((item) => item === tc.resultKind))
      .forEach((testcase, index) => {
        const newTestcase = testModels.getJson(testcase, this.i18n);
        const ReportTestCaseTransformer = classCreator(this.isIntegrationTest() && 'integrationTest');
        const transformer = new ReportTestCaseTransformer(
          newTestcase,
          funcVariables,
          typeSystem,
          this.i18n
        );
        try {
          transformer.transform(this.complete);
        } catch (error) {
          logger.error(`testCase transform error: fileId: ${func.fileId} function name: ${func.functionName} test case number: ${testcase.number}:`, error);
        }
        if (utility.isArrayType(transformer.input)) {
          transformer.input.forEach((input) => {
            // eslint-disable-next-line no-control-regex
            input.variable = input.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
            // 处理变量值显示格式
            if (input.value) {
              input.value = ` = ${input.value}`;
            }
          });
        }
        if (utility.isArrayType(transformer.output)) {
          transformer.output.forEach((output) => {
            // eslint-disable-next-line no-control-regex
            output.variable = output.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
            // 处理变量值显示格式
            if (output.result) {
              output.result = ` = ${output.result}`;
            }
            if (output.value) {
              output.value = ` = ${output.value}`;
            }
          });
        }
        const newInputDetails = [];
        const newOutputDetails = [];
        transformer.input.forEach((vobj) => {
          if (vobj.category && vobj.variable) {
            newInputDetails.push({
              category: `${vobj.category}:`,
              value: '',
              variable: '',
            });
            newInputDetails.push({
              category: '',
              value: vobj.value,
              variable: vobj.variable,
            });
          } else {
            newInputDetails.push(vobj);
          }
        });
        transformer.output.forEach((vobj) => {
          if (vobj.category && vobj.variable) {
            newOutputDetails.push({
              category: `${vobj.category}:`,
              value: '',
              result: '',
              variable: '',
            });
            newOutputDetails.push({
              category: '',
              value: vobj.value,
              result: vobj.result,
              variable: vobj.variable,
            });
          } else {
            newOutputDetails.push(vobj);
          }
        });
        const item = {
          index: `${index + 1}`.padStart(3, '0'),
          testId: testcase._id,
          resultKind: this.i18n.__(`tests.testResult.${BaseReport.getTestResultKindKey(
            testcase.resultKind === testModels.resultKind.noResult ? testModels.resultKind.succeed : testcase.resultKind
          )}`),
          details: {
            input: newInputDetails.length === 0 ? [{ category: '', variable: '', value: '入参为空值正常执行用例' }] : newInputDetails,
            output: newOutputDetails.length === 0 ? [{ category: '', variable: '', result: '该函数无输出结果，函数正常执行' }] : newOutputDetails,
          },
          identifier: testcase.identifier,
          functionId: func._id,
          filePath: file.path,
          fileName: file.fileName,
        };
        if (testcase.resultKind === resultKind.failed) {
          // 问题报告单展示
          this.failedCaseDetails.push(item);
        } else {
          // 只展示通过的用例
          testcasesDetails.push(item);
        }
      });
    return testcasesDetails;
  }
}

module.exports = Report17;
